package Core

import (
	"reflect"
	"fmt"
	"strings"
	"net/http"
	// "log"
	"runtime/debug"
)

// declare Router var
var Router *HxRouter

// declare HxRouter struct
type HxRouter struct {
	rules map[string]reflect.Value
	// error func
	Error HxRouteFunc
}

// Do router dispatch
func (this *HxRouter) Do(w http.ResponseWriter, r *http.Request) bool {
	pattern := r.Method + ":" + r.URL.Path
	lastMatchLength := 0
	matchedKey := ""
	for k, _ := range Router.rules {
		if !strings.HasPrefix(pattern, k) {
			continue
		}
		if lastMatchLength < len(k) {
			lastMatchLength = len(k)
			matchedKey = k
		}
	}
	if matchedKey == "" {
		return false
	}
	return this.exec(w, r, Router.rules[matchedKey])
}

// Do error dispatch
func (this *HxRouter) Err(w http.ResponseWriter, r *http.Request, e string) {
	if this.Error != nil {
		r.Header.Set("X-Error", e)
		this.exec(w, r, reflect.ValueOf(this.Error))
		return
	}
	status := http.StatusNotFound
	// only empty error means 404
	if e != "" {
		status = http.StatusServiceUnavailable
	}
	w.WriteHeader(status)
	w.Write([]byte(e))
	if status >= 500 {
		w.Write(debug.Stack())
	}
	Logger.Log(r.RemoteAddr, `- "`, r.Method, r.RequestURI, r.Proto, `"`, status)
}

// execute route rule
func (this *HxRouter) exec(w http.ResponseWriter, r *http.Request, rv reflect.Value) bool {
	args := make([]reflect.Value, 2)
	rs := Response(w)
	args[0] = reflect.ValueOf(Request(r))
	args[1] = reflect.ValueOf(rs)
	rv.Call(args)
	Logger.Log(r.RemoteAddr, `- "`, r.Method, r.RequestURI, r.Proto, `"`, rs.Status)
	return true
}

// sync route table
func (this *HxRouter) Sync() {
	routeTable := Config.Map("route_table")
	for k, v := range routeTable {
		cmd := fmt.Sprint(v)
		// log.Println("route table sync @ " + k, v)
		// invoker rule
		if strings.HasPrefix(cmd, "[I]") {
			tmp := strings.Split(strings.TrimPrefix(cmd, "[I]"), ".")
			structName := strings.Join(tmp[:len(tmp) - 1], ".")
			methodName := tmp[len(tmp) - 1]
			this.rules[k] = reflect.ValueOf(parseInvokeRouteFunc(structName, methodName))
			continue
		}
		// behavior rule
		if strings.HasPrefix(cmd, "[B]") {
			cmd = strings.TrimPrefix(cmd, "[B]")
			this.rules[k] = reflect.ValueOf(parseBehaviorRouteFunc(cmd))
			continue
		}
	}
}

// declare HxRouteFunc
type HxRouteFunc func (request *HxRequest, response *HxResponse)

// create new *HxRouter
func newRouter() {
	Router = &HxRouter{}
	Router.rules = make(map[string]reflect.Value)
}

// parse invoker rule to HxRouteFunc
func parseInvokeRouteFunc(structName, methodName string) HxRouteFunc {
	// check struct
	rv := structTree[structName]
	if !rv.IsValid() {
		panic("invalid struct: " + structName)
	}
	// check struct method
	rm := rv.MethodByName(methodName)
	if !rm.IsValid() {
		panic("invalid struct method :" + structName + "->" + methodName)
	}
	// return func, invoke method
	return func(request *HxRequest, response *HxResponse) {
		args := make([]reflect.Value, 2)
		args[0] = reflect.ValueOf(request)
		args[1] = reflect.ValueOf(response)
		rm.Call(args)
		if !response.IsDone {
			response.Do()
		}
	}
}

// parse behavior rule to HxRouteFunc
func parseBehaviorRouteFunc(behavior string) HxRouteFunc {
	b := &HxBehavior{}
	b.Behavior = behavior
	// return func, do behavior
	return func(request *HxRequest, response *HxResponse) {
		// check behavior
		if e := b.IsValid(); e != nil {
			panic("invalid behavior '" + behavior + "' " + fmt.Sprint(e))
		}
		b.Request = request
		b.Response = response
		b.cssFiles = map[string]string{}
		b.jsFiles = map[string]string{}
		b.viewData = make(map[string]interface {})
		b.Do()
		if !response.IsDone {
			response.Do()
		}
	}
}

// Set route rule
func SetRoute(url string, pattern string) {
	Config.Set("route_table." + url, pattern)
}

